/***
 * * PROJECT
 * *    ATaG
 * * VERSION
 * *    $Revision: 1.2 $
 * * DATE
 * *    $Date: 2008/10/07 12:57:37 $
 * * LOG
 * *    $Log: RandomTaskAssigner.java,v $
 * *    Revision 1.2  2008/10/07 12:57:37  animesh
 * *    removed GPL License [will re-add to all files later]
 * *
 * *    Revision 1.1  2007/06/01 20:53:20  animesh
 * *    bulk update.
 * *
 * *
 * *
 * *   ATaG (Abstract Task Graph) - Data-driven Macroprogramming for
 * *                                Sensor Networks
 * *
 ***/

package atag.compiler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

import atag.common.ATaGProgram;
import atag.common.ATaGTaskDeclaration;
import atag.common.NetworkTopology;
import atag.runtime.NodeInfo;
import atag.runtime.ln.nodeAttributes.IntegerAttribute;
import atag.runtime.ln.nodeAttributes.StringAttribute;

/**
 * "Sticky" task assignment
 * 
 * @author Animesh
 */
public class RandomTaskAssigner implements TaskAssigner{

	private Random randomizer;

	/** Creates a new instance of RandomTaskAssigner */
	public RandomTaskAssigner() {
	}
	/**
	 * The function that assigns tasks.
	 * @param aP The ATaG program
	 * @param itP The ITaG version of aP
	 * @param nTopo The network, with all nodes
	 */
	public void assignTasks(ATaGProgram aP, ITaGProgram itP, ComposedATaG caProgram,
			NetworkTopology nTopo, int randomSeed) {

		//TODO initialize the random Number Generator using randomSeed
		this.randomizer = new Random(randomSeed);

		// Temporary data structure to store partial task assignments
		HashMap<Integer, Set<Integer>> partialAssignment = new HashMap<Integer, Set<Integer>>();
		Iterator<NodeInfo> itNodes = nTopo.getNodes().iterator();
		while (itNodes.hasNext()) {
			partialAssignment.put(itNodes.next().getMyId(),
					new HashSet<Integer>());
		}

		// first, assign the tasks which are "easy to assign"
		for (ATaGTaskDeclaration at : aP.getTaskList()) {

			// Handle External Tasks
			if(at.getFiringRule()[0] == ATaGTaskDeclaration.FIRING_EXTERN)
			{
				continue;
			}

			// fault tolerant tasks go to next loop
			if (at.getFaultTolerance() == ATaGTaskDeclaration.FAULTTOLERANCE_REPLICATION){
				continue;
			}

			int instType = at.getLocation()[0];
			int instParam = at.getLocation()[1];
			int atID = at.getID();

			if (instType == ATaGTaskDeclaration.INSTANTIATION_ONE_ON_NODE_ID) {
				// instantiate on that node
				// nTopo.getNodeInfoByID(instParam).assignTask(atID);
				partialAssignment.get(instParam).add(atID);
				at.assign(instParam);
			} else if ((instType == ATaGTaskDeclaration.INSTANTIATION_NODES_PER_INSTANCE)
					&& (instParam == 1)) {
				// instantiate on one of each
				if (at.getNeededAttribute() != null) {
					// find all nodes with that attribute, and assign this task
					// on them
					for (NodeInfo ni : nTopo.getNodesWithAbility(at
							.getNeededAttribute())) {
						// ni.assignTask(at.getID());
						partialAssignment.get(ni.getMyId()).add(at.getID());
						at.assign(ni.getMyId());
					}
				} else {
					// no needed Attribute. Assign on all.
					for (NodeInfo ni : nTopo.getNodes()) {
						//						ni.assignTask(at.getID());
						partialAssignment.get(ni.getMyId()).add(at.getID());
						at.assign(ni.getMyId());
					}
				}
			} else {
				// nothing. Do nothing for this task in first pass
			}
		}

		// second pass. Assign tasks that have other instantiation rules
		for (ATaGTaskDeclaration at : aP.getTaskList()) {

			// Handle External Tasks
			if(at.getFiringRule()[0] == ATaGTaskDeclaration.FIRING_EXTERN)
			{
				continue;
			}

			int instType = at.getLocation()[0];
			int instParam = at.getLocation()[1];
			int atID = at.getID();

			if (at.getAssignedTo().size() == 0) {
				boolean faultTolerant = false;
				if (at.getFaultTolerance() == ATaGTaskDeclaration.FAULTTOLERANCE_REPLICATION){
					faultTolerant = true;
				}
				// not assigned yet. Assign now
				switch (instType) {
				case ATaGTaskDeclaration.INSTANTIATION_NODES_PER_INSTANCE:
					if (instParam == 1 && !faultTolerant) {
						// currently dies.. replace with code that handles other
						// values later
						System.err.println("Why is task" + at.getName()
								+ "with instantiation type "
								+ "nodes-per-instance:1 not assigned yet?");
						System.exit(-5);
					} else {
						ArrayList<NodeInfo> targetNodes;

						if (at.getNeededAttribute() == null) {
							targetNodes = nTopo.getNodes();
						} else {
							// find all nodes with that attribute
							targetNodes = nTopo.getNodesWithAttribute(at.getNeededAttribute());
						}

						if (!faultTolerant){
							// instantiate the tasks regularly
							// note the stepsize in the for loop
							for (int i = 0; i < targetNodes.size(); i = i + instParam) {
								NodeInfo ni = targetNodes.get(i);
								// ni.assignTask(at.getID());
								partialAssignment.get(ni.getMyId()).add(at.getID());
								at.assign(ni.getMyId());
							}
						} else {
							assignFaultTolerantTask(partialAssignment, at, targetNodes);
						}

					}
					break;
				case ATaGTaskDeclaration.INSTANTIATION_PARTITION_PER_INSTANCE:
					// Iterate over region-ids of nodes with the same region label                      
					for (int regionID: nTopo.getRegionIDsInRegion(at.getMyRegionName())) {
						if(regionID == -1) { // Invalid location
							continue;
						}
						ArrayList<NodeInfo> tempTarget = nTopo.getNodesInRegion(at.getMyRegionName(), regionID);
						for (int i = 0; i < instParam; i++) {
							// assign tasks one by one. Total instParam tasks

							// lines below should be changed to the one below
							// int targetNode = (int) (this.randomizer.nextDouble() * nTopo
							// .getNumberOfNodes());
							if (!faultTolerant){
								int targetNode = (int) (this.randomizer.nextDouble() * tempTarget.size());
								NodeInfo ni = tempTarget.remove(targetNode);
								//ni.assignTask(at.getID());
								partialAssignment.get(ni.getMyId()).add(at.getID());
								at.assign(ni.getMyId());
							} else {
								assignFaultTolerantTask(partialAssignment, at, tempTarget);
							}
						}
					}
					break;
				case ATaGTaskDeclaration.INSTANTIATION_ONE_ANYWHERE:
					int destinationNode;
					// completely random assignment -- random selects a number
					// in [0,1)
					destinationNode = (int) (this.randomizer.nextDouble() * nTopo
							.getNumberOfNodes());
					NodeInfo ni = nTopo.getNodeInfoByID(destinationNode);
					//					ni.assignTask(at.getID());
					partialAssignment.get(ni.getMyId()).add(at.getID());
					at.assign(destinationNode);
					break;
				case ATaGTaskDeclaration.INSTANTIATION_NODES_PER_INSTANCE_FRACTIONAL:
					// do nothing.. allow this to run into default. Fix later
				default:
					System.err.println("Illegal instantiation type " + at.getInstType() + " for task" + at.getName());
					System.exit(-5);
				}
			}
		}

		// Assigning tasks to nodes
		itNodes = nTopo.getNodes().iterator();
		while (itNodes.hasNext()) {
			NodeInfo nodeInfo = itNodes.next();
			nodeInfo.assignTasks(partialAssignment.get(nodeInfo.getMyId()));
		}

		return;
	}
	/**
	 * @param partialAssignment
	 * @param at
	 * @param targetNodes
	 */
	private void assignFaultTolerantTask(
			HashMap<Integer, Set<Integer>> partialAssignment,
			ATaGTaskDeclaration at, ArrayList<NodeInfo> targetNodes) {
		// fault tolerant task requires j2se node
		Iterator<NodeInfo> it = targetNodes.iterator();
		while (it.hasNext()){
			NodeInfo node = it.next();
			if (!node.getAttributeByName("type").equals("j2se")){
				it.remove();
			}
		}

		if (targetNodes.size() == 0){
			// TODO Oh! Snap! No j2se nodes!
		} else if (targetNodes.size() == 1) {
			// only room for a master node, and this is the one!
			NodeInfo ni = targetNodes.get(0);
			partialAssignment.get(ni.getMyId()).add(at.getID());
			at.assign(ni.getMyId());
			ni.addNodeAttribute(new IntegerAttribute("ft-node-type", 0));
		} else {
			// master and slave!
			Random rand = new Random();
			int master = rand.nextInt(targetNodes.size());
			int slave = rand.nextInt(targetNodes.size());
			while (master == slave) slave = rand.nextInt(targetNodes.size());
			NodeInfo masterNode = targetNodes.get(master);
			NodeInfo slaveNode = targetNodes.get(slave);
			String masterIP = (String) masterNode.getAttributeByName("physicaladdress");
			String slaveIP = (String) slaveNode.getAttributeByName("physicaladdress");

			masterNode.addNodeAttribute(new IntegerAttribute("ft-node-type",1));
			masterNode.addNodeAttribute(new StringAttribute("ft-pair-ip", slaveIP));

			slaveNode.addNodeAttribute(new IntegerAttribute("ft-node-type",2));
			slaveNode.addNodeAttribute(new StringAttribute("ft-pair-ip", masterIP));

			partialAssignment.get(masterNode.getMyId()).add(at.getID());
			partialAssignment.get(slaveNode.getMyId()).add(at.getID());
			at.assign(masterNode.getMyId());
			at.assign(slaveNode.getMyId());
		}
	}


}
